// 
// Copyright (c) 2006-2008 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;

namespace Anculus.Core
{
	public class FileLogBackend : AbstractLogBackend
	{
		protected long currentLogSize;
		protected long maxLogSize;

		protected StreamWriter writer;
		
		public FileLogBackend (string filename)
			: this (filename, 1024, false, true)
		{
		}
		
		public FileLogBackend (string filename, int maxLogSize)
			: this (filename, maxLogSize, false, true)
		{
		}
		
		public FileLogBackend (string fileName, int maxLogSize, bool deletePreviousLog, bool keepAlive)
		{
			if (fileName == null)
				throw new ArgumentNullException ("fileName");
			
			currentLogSize = 0;
			
			//check if the old logfile needs to be removed
			if (deletePreviousLog && File.Exists (fileName))
				File.Delete (fileName);

			FileName = fileName;
			MaximumLogSize = maxLogSize;
			KeepAlive = keepAlive;
		}
		
		public override bool IsOpen
		{
			get { return writer != null && writer.BaseStream != null && writer.BaseStream.CanWrite; }
		}
		
		public string FileName { get; protected set; }
		
		public bool KeepAlive { get; set; }

		public long MaximumLogSize
		{
			get { return maxLogSize / 1024; }
			set {
				if (value < 10)
					throw new ArgumentException ("maxLogSize must be at least 10 kB.");
				maxLogSize = value * 1024;
			}
		}

		public override void Open ()
		{
			if (writer != null)
				return; //stream is already open (KeepAlive = true)
			
			if (File.Exists (FileName)) {
				currentLogSize = new FileInfo (FileName).Length;
				if (currentLogSize > maxLogSize)
					LogFull ();
				
				writer = new StreamWriter (new FileStream (FileName, FileMode.Append, FileAccess.Write));
			} else {
				writer = new StreamWriter (new FileStream (FileName, FileMode.Create, FileAccess.Write));
			}
		}

		public override void Close ()
		{
			Close (false); //close only if KeepAlive = false
		}

		public override void Write (string raw)
		{
			if (raw == null)
				return;
			
			writer.Write (raw);
			writer.Flush ();
			
			currentLogSize += raw.Length;
			if (currentLogSize > maxLogSize)
				LogFull ();
		}
		
		protected virtual void LogFull ()
		{
			//force close
			Close (true);
			
			//if the log is full, we remove 50% of it's content
			
			try {
				//open the logfile and find the position of the first newline after 50%
				using (BinaryReader reader = new BinaryReader (File.OpenRead (FileName))) {
					reader.BaseStream.Seek (maxLogSize / 2, SeekOrigin.End);
					
					//check if there is a newline, but only check the first 1024 chars
					//to make sure we aren't taking too much time
					int seekahead = 1024;
					while (seekahead > 0) {
						
						byte b = reader.ReadByte ();
						if (b == (byte)10 || b == (byte)13) // \r or \n
							break;
						
						seekahead--;
					}
					
					byte[] buffer = new byte[8192];
					
					//create a temp file and copy the old logs
					string tmpFileName = Path.GetTempFileName ();
					using (BinaryWriter writer = new BinaryWriter (File.Create (tmpFileName))) {
						int read = reader.Read (buffer, 0, 8192);
						while (read > 0) {
							writer.Write (buffer, 0, read);
							
							read = reader.Read (buffer, 0, 8192);
						}
					}
					
					//delete the current logfile and move the new
					File.Delete (FileName);
					File.Move (tmpFileName, FileName);
				}
			} catch {
				//ignore
			}
		}
		
		public void Close (bool force)
		{
			//check if the underlying file must be kept open
			if (KeepAlive && !force)
				return;

			if (writer != null) {
				try {
					writer.Flush ();
					writer.Close ();
					writer.Dispose ();
				} finally {
					writer = null;
				}
			}
		}
		
		/// <summary>
		/// Returns a hash code for this instance.
		/// </summary>
		/// <returns>
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
		/// </returns>
		public override int GetHashCode ()
		{
			return typeof (FileLogBackend).GetHashCode () ^ FileName.GetHashCode ();
		}
	}
}